﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Reflection;
using System.IO;
using RazorEngine;
using PartyCraftServer.Web.Pages;
using System.Web;

namespace PartyCraftServer.Web
{
    public class WebService
    {
        public HttpListener Listener { get; set; }
        public Dictionary<string, string> ContentTypes;
        public Dictionary<string, AuthenticatedEndPoint> LoggedInUsers;

        public void Start()
        {
            if (!PartyCraft.Settings.GetBoolean("server.web.enabled"))
                return;

            ContentTypes = new Dictionary<string, string>();
            PopulateContentTypes();
            LoggedInUsers = new Dictionary<string, AuthenticatedEndPoint>();

            Listener = new HttpListener();
            Listener.Prefixes.Add("http://+:" + PartyCraft.Settings["server.web.port"] + "/");
            Listener.Start();
            Listener.BeginGetContext(GetContext, Listener);
        }

        private void PopulateContentTypes()
        {
            ContentTypes.Add(".html", "text/html");
            ContentTypes.Add(".css", "text/css");
            ContentTypes.Add(".js", "text/javascript");
            ContentTypes.Add(".bmp", "image/bmp");
            ContentTypes.Add(".gif", "image/gif");
            ContentTypes.Add(".jpg", "image/jpeg");
            ContentTypes.Add(".jpeg", "image/jpeg");
            ContentTypes.Add(".png", "image/png");
            ContentTypes.Add(".ico", "image/vnd.microsoft.icon");
        }

        private void GetContext(IAsyncResult result)
        {
            HttpListenerContext context = Listener.EndGetContext(result);
            context.Response.StatusCode = 200;
            context.Response.ContentEncoding = Encoding.UTF8;
            var query = HttpUtility.ParseQueryString(context.Request.Url.Query);

            // Revalidate authentication
            if (LoggedInUsers.ContainsKey(context.Request.RemoteEndPoint.ToString()))
                LoggedInUsers[context.Request.RemoteEndPoint.ToString()].ExpirationTime = DateTime.Now.AddHours(1);

            try
            {
                string path = context.Request.Url.LocalPath;
                if (path == "/")
                    path = "/Index";
                if (path.EndsWith("/"))
                    path = path.Remove(path.Length - 1);
                if (path.StartsWith("images/"))
                    path = "/" + path;

                // Search for handler
                string TypePath = path.Replace('/', '.');
                TypePath = "PartyCraftServer.Web.Pages" + TypePath;
                Type t = Type.GetType(TypePath, false, true);
                if (t != null)
                {
                    if (typeof(WebHandler).IsAssignableFrom(t))
                    {
                        WebHandler iwh = (WebHandler)Activator.CreateInstance(t);
                        iwh.HandleRequest(context);
                    }
                    else if (t.IsAssignableFrom(typeof(IPageHandler)))
                    {
                        IPageHandler iph = (IPageHandler)Activator.CreateInstance(t);
                        string page = WebService.ExecuteRazor(iph.HandlePage(context), new RazorModel(context, query));
                        byte[] b = Encoding.UTF8.GetBytes(page);
                        context.Response.ContentEncoding = Encoding.UTF8;
                        context.Response.ContentType = "text/html; charset=utf-8";
                        context.Response.ContentLength64 = b.Length;
                        context.Response.OutputStream.Write(b, 0, b.Length);
                    }
                }
                else
                {
                    // Search for API handler
                    if (path.Length > 0 && path.Substring(1).Contains("/"))
                    {
                        string controllerPath = path.Remove(path.LastIndexOf("/"));
                        controllerPath = controllerPath.Replace("/", ".");
                        Type controllerType = Type.GetType("PartyCraftServer.Web.Pages" + controllerPath, false, true);
                        if (controllerType == null)
                            controllerType = Type.GetType("PartyCraftServer.Web.API" + controllerPath, false, true);
                        if (controllerType != null && controllerType.BaseType == typeof(WebController))
                        {
                            string method = path.Substring(path.LastIndexOf("/") + 1);
                            var methods = controllerType.GetMethods().Where(m => m.Name.ToLower() == method);
                            if (methods.Count() != 0)
                            {
                                foreach (MethodInfo mi in methods)
                                {
                                    WebController wc = (WebController)Activator.CreateInstance(controllerType);
                                    wc.Context = context;
                                    if (mi.GetParameters().Length == 0)
                                    {
                                        mi.Invoke(wc, new object[0]);
                                        Listener.BeginGetContext(GetContext, Listener);
                                        return;
                                    }
                                    else if (mi.GetParameters().Length == 1 &&
                                        mi.GetParameters()[0].ParameterType == typeof(HttpListenerContext))
                                    {
                                        mi.Invoke(wc, new object[] { context });
                                        Listener.BeginGetContext(GetContext, Listener);
                                        return;
                                    }
                                    else
                                    {
                                        ParameterInfo[] pic = mi.GetParameters();
                                        if (pic.Length < query.AllKeys.Length)
                                            continue;
                                        List<object> parameters = new List<object>();
                                        bool hasAllParams = true;
                                        foreach (ParameterInfo pi in pic)
                                        {
                                            if (!query.AllKeys.Contains(pi.Name.ToLower()))
                                            {
                                                hasAllParams = false;
                                                break;
                                            }
                                            else
                                            {
                                                // Parse parameter
                                                if (pi.ParameterType == typeof(string))
                                                {
                                                    parameters.Add(query[pi.Name.ToLower()]);
                                                }
                                                else if (pi.ParameterType.GetMethod("Parse", new Type[] { typeof(string) }) != null)
                                                {
                                                    parameters.Add(pi.ParameterType.GetMethod("Parse", new Type[] { typeof(string) }).Invoke(Activator.CreateInstance(
                                                        pi.ParameterType), new object[] { query[pi.Name.ToLower()] }));
                                                }
                                                else
                                                {
                                                    hasAllParams = false;
                                                    break;
                                                }
                                            }
                                        }
                                        if (hasAllParams)
                                        {
                                            mi.Invoke(wc, parameters.ToArray());
                                            try
                                            {
                                                context.Response.Close();
                                            }
                                            catch { }
                                            return;
                                        }
                                        else
                                        {
                                            continue;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // Handle embedded cshtml files
                    string file = "PartyCraftServer.Web.Pages" + path.Replace("/", ".");
                    string sResult = ExecuteRazor(file + ".cshtml", new RazorModel(context, query));
                    if (sResult != null)
                    {
                        byte[] b = Encoding.UTF8.GetBytes(sResult);
                        context.Response.ContentEncoding = Encoding.UTF8;
                        context.Response.ContentType = "text/html; charset=utf-8";
                        context.Response.ContentLength64 = b.Length;
                        context.Response.OutputStream.Write(b, 0, b.Length);
                    }
                    else
                    {
                        if (path == "/Index")
                            path = "/Index.html";
                        try
                        {
                            // Search for embedded file
                            file = "PartyCraftServer.Web.Pages" + path.Replace("/", ".");
                            Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(file);
                            string str = Path.GetExtension(path);
                            if (ContentTypes.ContainsKey(Path.GetExtension(path)))
                                context.Response.ContentType = ContentTypes[Path.GetExtension(path)];
                            byte[] buffer = new byte[s.Length];
                            s.Read(buffer, 0, buffer.Length);
                            s.Close();
                            context.Response.OutputStream.Write(buffer, 0, buffer.Length);
                            context.Response.OutputStream.Close();
                        }
                        catch
                        {
                            if (File.Exists(Directory.GetCurrentDirectory() + context.Request.Url.LocalPath))
                            {
                                Stream s = File.Open(Directory.GetCurrentDirectory() + path, FileMode.Open);
                                byte[] buffer = new byte[s.Length];
                                s.Read(buffer, 0, (int)s.Length);
                                s.Close();
                                context.Response.OutputStream.Write(buffer, 0, buffer.Length);
                                context.Response.OutputStream.Close();
                            }
                            else
                            {
                                // 404
                                t = typeof(Error404Handler);
                                IPageHandler iph = (IPageHandler)Activator.CreateInstance(t);
                                string page = WebService.ExecuteRazor(iph.HandlePage(context), new RazorModel(context, query));
                                byte[] b = Encoding.UTF8.GetBytes(page);
                                context.Response.ContentEncoding = Encoding.UTF8;
                                context.Response.ContentType = "text/html; charset=utf-8";
                                context.Response.ContentLength64 = b.Length;
                                context.Response.StatusCode = 404;
                                context.Response.StatusDescription = "Not Found";
                                context.Response.OutputStream.Write(b, 0, b.Length);
                            }
                        }
                    }
                }
            }
            catch
            { }
            finally
            {
                Listener.BeginGetContext(GetContext, Listener);
            }
        }

        public static string ExecuteRazor(string ScriptName)
        {
            Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(ScriptName);
            if (s == null)
            {
                s = Assembly.GetExecutingAssembly().GetManifestResourceStream("PartyCraftServer.Web.Pages." + ScriptName);
                if (s == null)
                {
                    s = Assembly.GetExecutingAssembly().GetManifestResourceStream("PartyCraftServer.Web.Pages.razor." + ScriptName);
                    if (s == null)
                        return null;
                }
            }
            StreamReader sr = new StreamReader(s);
            string script = sr.ReadToEnd();
            return Razor.Parse(script);
        }

        public static string ExecuteRazor(string ScriptName, dynamic Model)
        {
            Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(ScriptName);
            if (s == null)
            {
                s = Assembly.GetExecutingAssembly().GetManifestResourceStream("PartyCraftServer.Web.Pages." + ScriptName);
                if (s == null)
                {
                    s = Assembly.GetExecutingAssembly().GetManifestResourceStream("PartyCraftServer.Web.Pages.razor." + ScriptName + ".cshtml");
                    if (s == null)
                        return null;
                }
            }
            StreamReader sr = new StreamReader(s);
            string script = sr.ReadToEnd();
            return Razor.Parse(script, Model);
        }

        static Dictionary<string, string> ColorCodes;

        public static string ColorCodeHtml(string text)
        {
            if (ColorCodes == null)
                PopulateColorCodes();
            string html = "";
            if (text.Contains("§"))
            {
                string[] parts = text.Split('§');
                if (text.StartsWith("§"))
                {
                    parts = parts.Skip(1).ToArray();
                    foreach (string s in parts)
                        html += "<span style=\"color: " + ColorCodes[s.Substring(0, 1)] + ";\">" + HttpUtility.HtmlEncode(s.Substring(1)) + "</span>";
                }
                else
                {
                    html += parts[0];
                    parts = parts.Skip(1).ToArray();
                    foreach (string s in parts)
                        html += "<span style=\"color: " + ColorCodes[s.Substring(0, 1)] + ";\">" + HttpUtility.HtmlEncode(s.Substring(1)) + "</span>";
                }
            }
            else
                html = HttpUtility.HtmlEncode(text);
            return html;
        }

        private static void PopulateColorCodes()
        {
            ColorCodes = new Dictionary<string, string>();
            ColorCodes.Add("0", "#000");
            ColorCodes.Add("1", "#00a");
            ColorCodes.Add("2", "#0a0");
            ColorCodes.Add("3", "#0aa");
            ColorCodes.Add("4", "#a00");
            ColorCodes.Add("5", "#a0a");
            ColorCodes.Add("6", "#fa0");
            ColorCodes.Add("7", "#aaa");
            ColorCodes.Add("8", "#555");
            ColorCodes.Add("9", "#55f");
            ColorCodes.Add("a", "#5f5");
            ColorCodes.Add("b", "#5ff");
            ColorCodes.Add("c", "#f55");
            ColorCodes.Add("d", "#f5f");
            ColorCodes.Add("e", "#ff5");
            ColorCodes.Add("f", "#fff");
        }
    }
}
